#include "service.h"

// gets statistic about how often every symbol is met in the file

unsigned long long sym_freq(FILE *input, unsigned long sym_array[])
{
    int var;
    unsigned long long total_sym = 0;
    rewind(input);
    for (var = fgetc(input); var != EOF; var = fgetc(input), total_sym++)
    {
	sym_array[var]++;
    }
    return total_sym;
}

// recursively creates Huffman codes for symbols

void build_table(node *root, unsigned char *entry, int length, unsigned char array[][256])
{
    if ((!root->left && !root->right)) // aren't we in a leaf?
    {
	entry[length] = '\0';
	strcat((char*) array[root->data], (char*) entry);
    }

    if (root->right)
    {
	entry[length] = '1';
	build_table(root->right, entry, length + 1, array);
    }

    if (root->left)
    {
	entry[length] = '0';
	build_table(root->left, entry, length + 1, array);
    }
}

// builds Huffman tree

int build_tree(node **root, unsigned long sym_array[])
{
    unsigned short int i;
    node *var1 = NULL, *var2 = NULL, *var3 = NULL;

    // building a list of nodes
    list *head = NULL;
    for (i = 0; i < 256; i++)
    {
	if (sym_array[i])
	{
	    node *tree = (node*) malloc(sizeof (node));

	    if (!tree)
	    {
		print_error(NO_FREE_MEMORY, (unsigned char*) "");
		return -1;
	    }

	    tree->data = i;
	    tree->weight = sym_array[i];
	    tree->left = tree->right = NULL;
	    // adding nodes to the list that sorts itself
	    add_to_list(&head, tree);
	}
    }

    // building a tree
    do
    {
	// if the get_from_list returns element with null weight - it's main root
	// and the very last node we have

	var1 = get_from_list(&head);
	if (!var1->weight)
	    *root = var2;

	var2 = get_from_list(&head);
	if (!var2->weight)
	    *root = var1;

	if (!(*root))
	{
	    // creating new node to be put into the list

	    var3 = (node*) malloc(sizeof (node));
	    if (!var3)
	    {
		print_error(NO_FREE_MEMORY, (unsigned char*) "");
		return -1;
	    }

	    var3->left = var1;
	    var3->right = var2;
	    var3->data = 0;
	    var3-> weight = var1->weight + var2->weight;

	    add_to_list(&head, var3);
	}
    }
    while (!(*root));


    /* In case there is only one symbol in the code the Huffman tree
     * has no leaves and that's bad. Very bad.
     * To overcome such situation we'll make the leafs
     * with our own hands..
     * Sorry for so "indian" code
     */

    if ((!(*root)->left) && (!(*root)->right))
    {
	for (i = 0; i < 256; i++)
	    if (sym_array[i])
		break;

	node *tree = (node*) malloc(sizeof (node));
	node *tree_fake = (node*) malloc(sizeof (node));

	if ((!tree) || (!tree_fake))
	{
	    print_error(NO_FREE_MEMORY, (unsigned char*) "");
	    return -1;
	}

	tree->data = i;
	tree_fake->data = i + 1;

	tree_fake->weight = tree->weight = 0;
	tree_fake->right = tree->right = NULL;
	tree_fake->left = NULL;
	tree->left = (*root);
	tree->right = tree_fake;
	(*root) = tree;
    }

    return 0;
}



// reads chars from archive file and uses its binary code symbols

inline void decode_to_file(node *root, FILE *in, unsigned long long int num_of_symbols, FILE *out)
{
    size_t i;
    node *tree_node;
    int offset = 7;
    unsigned char var;

    for (i = 0, tree_node = root, var = fgetc(in);; i++, var = fgetc(in), offset = 7)
    {
	if (!num_of_symbols)
	    break;

	for (; offset != -1; offset--)
	{
	    if (((var >> offset)&1))
	    {
		if (tree_node->right)
		{
		    tree_node = tree_node->right;
		    if (!tree_node->left && !tree_node->right)
		    {
			fputc(tree_node->data, out);
			num_of_symbols--;
			if (!num_of_symbols)
			    break;
			tree_node = root;
		    }
		}
	    }
	    else
	    {
		if (tree_node->left)
		{
		    tree_node = tree_node->left;
		    if (!tree_node->left && !tree_node->right)
		    {
			fputc(tree_node->data, out);
			num_of_symbols--;
			if (!num_of_symbols)
			    break;
			tree_node = root;
		    }
		}
	    }
	}
	// end of the file. archive is probably damaged
	if (feof(in))
	    break;
    }
}

int decodeAllFiles(FILE *f)
{
    size_t offset = 1, file_length;

    fseek(f, 0, SEEK_END);
    file_length = ftell(f);
    fseek(f, 2, SEEK_SET);
    do
    {
	decodeByOffset(f, offset - 1, 0);
	offset = ftell(f);
	fseek(f, -1, SEEK_CUR);
    }
    while (file_length != offset);
    return 0;
}

int testIntegrity(FILE *f)
{
    size_t offset = 1, file_length;
    int error_indicator = 0;

    fseek(f, 0, SEEK_END);
    file_length = ftell(f);
    fseek(f, 2, SEEK_SET);
    do
    {
	error_indicator += decodeByOffset(f, offset - 1, 1);
	offset = ftell(f);
	fseek(f, -1, SEEK_CUR);
    }
    while (file_length != offset);

    return error_indicator;
}

void deleteByFilenames(FILE *f, unsigned char** argv, int argc)
{
    int i, j = 0, num = argc - 3, buffer;
    FILE *tempFile;
    arfile *arroot = NULL;
    size_t *absPointerArray = malloc(sizeof (size_t) * num),
	    *relPointerArray = malloc(sizeof (size_t) * num);
    size_t offset, file_size;
    char template[] = "huffmanzipXXXXXX";

    fseek(f, 0, SEEK_END);
    file_size = ftell(f);

    if (buildArfileList(f, &arroot))
    {
	print_error(SUBTEST_FAIL, (unsigned char*) "");
	return;
    }

    for (i = 3; i < argc; i++)
	if (findFileInArchive(argv[i], arroot, &absPointerArray[j], &relPointerArray[j]))
	{
	    print_error(FILE_NOT_FOUND, argv[i]);
	    argv[i][0] = '\0';
	}
	else
	    j++;

    absPointerArray[j++] = file_size;
    sel_sort(absPointerArray, relPointerArray, num);

#ifdef DEBUG
    for (i = 0; i < j; i++)
	printf("abs: %zu\trel: %zu\n", absPointerArray[i], relPointerArray[i]);
#endif

    if (!(tempFile = fdopen(mkstemp(template), "wb")))
    {
	print_error(OPENING_TEMP_FILE, (unsigned char*) "");
	return;
    }

    i = 0;
    offset = 0;
    fseek(f, 0, SEEK_SET);
    do
    {
	offset = ftell(f);
	for (buffer = (int) fgetc(f); offset < (absPointerArray[i] + 2); buffer = (int) fgetc(f), offset++)
	{
	    if (buffer == EOF)
		break;
	    fputc(buffer, tempFile);
	}

	fseek(f, relPointerArray[i] - 1, SEEK_CUR);
	i++;
    }
    while (buffer != EOF);

    fclose(tempFile);
    fclose(f);
    if (unlink((char*) argv[2]))
    {
	print_error(REMOVING_FILE, argv[2]);
	return;
    }

    if (rename(template, (char*) argv[2]))
    {
	print_error(MOVING_FILE, (unsigned char*) template);
	return;
    }

    // deleting file in case it's empty

    if (!(f = openFileRB(argv[2])))
	return;

    fseek(f, 0, SEEK_END);
    file_size = ftell(f);

    if (file_size == 2)
    {
	if (unlink((char*) argv[2]))
	{
	    print_error(REMOVING_FILE, argv[2]);
	    return;
	}
    }

}

void decodeByFilenames(FILE* f, unsigned char** argv, int argc)
{
    arfile *arroot = NULL;
    size_t offset;
    int i;

    if (buildArfileList(f, &arroot))
    {
	print_error(SUBTEST_FAIL, (unsigned char*) "");
	return;
    }

    for (i = 3; i < argc; i++)
    {
	if (!findFileInArchive(argv[i], arroot, &offset, NULL))
	    decodeByOffset(f, offset + 2, 0);
	else
	    print_error(FILE_NOT_FOUND, argv[i]);
    }
    return;
}

int decodeByOffset(FILE *f, size_t offset, int useTempDir)
{
    unsigned char filename[MAX_FILENAME_LENGTH];

    enum
    {
	WAS_ENCODED = 0, WAS_COPIED
    } file_attr;
    int i = -1;
    unsigned long hash_saved, hash_new;
    size_t file_bytes;
    FILE *out;

    // [HZ] in the begging of archive file
    offset = !offset ? 2 : offset;

    fseek(f, offset, SEEK_SET);

    // skipping offset info
    fread(&file_bytes, sizeof (file_bytes), 1, f);

    // reading name
    do
    {
	i++;
	filename[i] = (unsigned char) fgetc(f);
    }
    while ((filename[i]) && (i < MAX_FILENAME_LENGTH));

    if (useTempDir == 0)
    {
	if (!(out = openFileWB(filename)))
	    return -1;
    }
    else
    {
	if (!(out = tmpfile()))
	{
	    print_error(OPENING_TEMP_FILE, (unsigned char*) "");
	    return -1;
	}
    }

    fread(&hash_saved, sizeof (hash_saved), 1, f);

    if (fgetc(f))
	file_attr = WAS_ENCODED;
    else
	file_attr = WAS_COPIED;

    if (WAS_COPIED == file_attr)
    {
	int buffer;
	size_t i = file_bytes - strlen((char*) filename) - sizeof (offset) - sizeof (hash_saved) - 2;
	for (buffer = fgetc(f); i; buffer = fgetc(f), i--)
	    putc(buffer, out);
    }

    if (WAS_ENCODED == file_attr)
    {
	unsigned long long num_of_symbols = 0;
	unsigned long array[256] = {0};
	node *root = NULL;
	int buffer, j;

	// unique symbols in stat
	// 0 means 256 because of overflow of char
	buffer = fgetc(f);
	j = buffer ? buffer : 256;

	for (i = 0; i < j; i++)
	{
	    unsigned long amount = 0;
	    unsigned char var;

	    var = fgetc(f);
	    fread(&amount, sizeof (amount), 1, f);

	    array[var] = amount;
	    num_of_symbols += (unsigned long long) amount;
	}

	build_tree(&root, array);

	decode_to_file(root, f, num_of_symbols, out);

#ifdef DEBUG
	print_sym_freq_stat(filename, array);
	psgraph_tree_print(root, 0);
#endif
	annihilate_tree(&root);
    }

    hash_new = adler32(out);

    // CRC failed
    if (hash_new != hash_saved)
    {
	print_error(CORRUPTED_FILE_IN_ARC, filename);
	return -1;
    }

    fclose(out);
    return 0;
}

// bit[] is a char string with huffman code of the current symbol

inline long long encode_to_file(unsigned char bit[], FILE *out, int mode, int reset)
{
    static long int bytes_written = 0;
    static char buffer = 0;
    static short int offset = 7;
    int i;

    // resets static variables when new file is to be encoded
    if (!reset)
    {
	// mode != 0 -> usual bytes, mode == 0 -> last byte
	if (mode)
	{
	    for (i = 0; bit[i]; offset--, i++)
	    {
		if ('1' == bit[i])
		{
		    buffer = buffer | (1 << offset);
		}
		if (!offset)
		{
		    bytes_written++;
		    putc(buffer, out);
		    offset = 8; // not 7 cause offset-- !
		    buffer = 0;
		}
	    }
	}
	else
	{
	    if (offset != 7)
	    {
		bytes_written++;
		putc(buffer, out);
	    }
	}

	return bytes_written;
    }
    else
    {
	bytes_written = 0;
	buffer = 0;
	offset = 7;
	return 0;
    }
}

// function encodes file pointed by f, using symbols freq table and table with huffman codes
// and writes encoded information to out

void encode(FILE *f, unsigned long sym_array[], unsigned char table[][256], FILE *out)
{
    int i, var;
#ifdef DEBUG
    unsigned long long bytes_written = 0;
#endif
    unsigned char different_symbols = 0;
    different_symbols = count_symbols(sym_array);

    // number of symbols to be written to file
    putc(different_symbols, out);

    for (i = 0; i < 256; i++)
    {
	if (sym_array[i])
	{
	    int symbol = i;
	    putc(symbol, out);
	    fwrite(&sym_array[i], sizeof (sym_array[0]), 1, out);
	}
    }

    rewind(f);
    for (var = fgetc(f); var != EOF; var = fgetc(f))
    {
	encode_to_file(table[var], out, 1, 0);
    }
#ifdef DEBUG
    bytes_written = encode_to_file((unsigned char*) "", out, 0, 0);
    printf("\nInformation is compressed to %lld bytes.\n", bytes_written);
#else
    // writing the vary last remaining byte if needed
    encode_to_file((unsigned char*) "", out, 0, 0);
#endif
}

/*********************
 *		    file structure:
 *
 *  [HZ]{file1}[offset][name][adler32hash][coded or copied?][num_of_symb][stat][data]{file2}...[data]
 *
 *********************/

// looks awful but no sense to change

int append_files(int count, unsigned char *args[])
{
    // name of archive file and buffer for file names
    unsigned char file_name[MAX_FILENAME_LENGTH];
    int i, j, flag_files_encoded = 0;
    FILE *f = NULL, *out = NULL;
    arfile *ar_root = NULL;
    file_name[0] = '\0';

    // choice either we add info to archive that already exists
    // or we have to create a new one.
    // in case of new archive the extention is added to the name given by user when needed
    if (!(out = fopen((char*) args[2], "r+")))
    {
	int len = strlen((char*) args[2]);
	file_name[0] = '\0';
	strncat((char*) file_name, (char*) args[2], MAX_FILENAME_LENGTH - 1);

	// maybe user has already added extention?
	if (len > 3)
	{
	    if ((file_name[len - 3] != '.') || (file_name[len - 2] != 'h') || (file_name[len - 1] != 'z'))
	    {
		strcat((char*) file_name, ".hz");
	    }
	}
	else
	{
	    strcat((char*) file_name, ".hz");
	}

	if (!(out = openFileWB(file_name)))
	    return -1;

	// file identifier for linux magic or some other stuff like that
	putc('H', out);
	putc('Z', out);
    }
    else
    {
	if(testIfRightArchiveFormat(out, args[2]))
	    return -1;
    }

    for (i = 3; i < count; i++)
    {
	if ((f = fopen((char*) args[i], "rb")))
	{
	    unsigned long long total_sym;
	    unsigned long adler32hash, sym_array[256] = {0};
	    unsigned char table[256][256] = {
		{0}
	    }, entry[256];
	    size_t offset_before_writing,
		    offset_after_writing, offset_to_write;
	    node *root = NULL;
	    int flag = -1;

	    enum
	    {
		ENCODING = 0, COPYING = 1
	    } MODE;
	    int stat_symbols;

	    // checking intergity of the existing archive:
	    // file_name[0] is '\0' if archive existed
	    if (!file_name[0])
	    {
		if (buildArfileList(out, &ar_root))
		{
		    print_error(SUBTEST_FAIL, args[2]);
		    return -1;
		}
	    }

	    // stats for input file
	    total_sym = sym_freq(f, sym_array);

	    stat_symbols = count_symbols(sym_array);

	    // in case statistic for a file is greater than the file itself
	    // we'd better just copy it binary to archive
	    if (stat_symbols * (sizeof (unsigned long) + 1) < total_sym)
		MODE = ENCODING;
	    else
		MODE = COPYING;

	    if (MODE == ENCODING)
	    {
		if (-1 == build_tree(&root, sym_array))
		{
		    return -1;
		}
#ifdef DEBUG
		print_sym_freq_stat(args[i], sym_array);
		printf("Total symbols %lld\n", total_sym);
		printf("\n\tGraphical tree preview\n**\n");
		psgraph_tree_print(root, 0);
#endif
		build_table(root, entry, 0, table);
	    }

	    fseek(out, 0, SEEK_END);

	    offset_before_writing = ftell(out);

	    // taking place for offset info, will be changed later on
	    fwrite(&offset_before_writing, sizeof (offset_before_writing), 1, out);


	    // checking if there is no other files with such name
	    strncpy((char*) file_name, (char*) args[i], MAX_FILENAME_LENGTH);
	    do
	    {
		arfile *new_element = malloc(sizeof (arfile));
		new_element->name[0] = '\0';
		new_element->left = new_element->right = NULL;
		strncpy((char*) new_element->name, (char*) file_name, MAX_FILENAME_LENGTH);
		if (-1 == addToArfileTree(&ar_root, new_element))
		{
		    print_error(NOTUNIQUE_NAME, (unsigned char*) file_name);
		    printf("Input another name for file %s: ", file_name);
		    fgets((char*) file_name, MAX_FILENAME_LENGTH, stdin);
		    for (j = 0; file_name[j] != '\n'; j++);
		    file_name[j] = '\0';
		}
		else
		    flag = 0;
	    }
	    while (-1 == flag);

	    // writing the name of the file
	    for (j = 0; file_name[j]; j++)
		fputc((int) file_name[j], out);
	    fputc((int) '\0', out);

	    // kinda CRC
	    adler32hash = adler32(f);
	    fwrite(&adler32hash, sizeof (adler32hash), 1, out);

	    if (MODE == ENCODING)
	    {
		//char sign that the file is encoded
		putc(1, out);

		// stat & main data
		encode(f, sym_array, table, out);
	    }

	    if (MODE == COPYING)
	    {
		int buffer;
		rewind(f);
		//char sign that the file was just copied
		putc(0, out);
		for (buffer = fgetc(f); buffer != EOF; buffer = fgetc(f))
		    putc(buffer, out);

	    }

	    offset_after_writing = ftell(out);
	    fseek(out, offset_before_writing, SEEK_SET);

	    offset_to_write = offset_after_writing - offset_before_writing;

	    // changing offset due to new data that was written
	    fwrite(&offset_to_write, sizeof (offset_to_write), 1, out);

	    annihilate_tree(&root);

	    if (MODE == ENCODING)
	    {
		// reset buffer, offset and etc... needed in case of encoding
		// more than one file
		encode_to_file((unsigned char*) "", NULL, 0, 1);
	    }
	}
	else
	{
	    print_error(OPENING_FILE, args[i]);
	    return -1;
	}
	flag_files_encoded++;
    }

    if (!flag_files_encoded) //no files to archive
	print_error(NO_FILES, (unsigned char*) "");

    annihilate_arfiletree(ar_root);
    fclose(out);
    return 0;
}
